#include <iostream>
#include <map>

#include <include/core/SkCanvas.h>
#include <include/core/SkDrawable.h>
#include <include/core/SkOverdrawCanvas.h>
#include <include/core/SkPath.h>
#include <include/core/SkPicture.h>
#include <include/core/SkRegion.h>
#include <include/core/SkSurface.h>
#include <include/core/SkTextBlob.h>
#include <include/effects/SkOverdrawColorFilter.h>
#include <skia_control/button.h>
#include <skia_control/text.h>
#include <skia_framework.h>
#include <include/utils/SkNoDrawCanvas.h>

class OverdrawListenedCanvas : public SkCanvasVirtualEnforcer<SkNoDrawCanvas> {
public:
    OverdrawListenedCanvas(SkCanvas &canvas, SkCanvas *canvas2)
        : SkCanvasVirtualEnforcer<SkNoDrawCanvas>(canvas.imageInfo().width(), canvas.imageInfo().height()),
        canvas_(canvas), canvas2_(*canvas2), pcanvas2_(canvas2)
    {
    }

    ~OverdrawListenedCanvas()
    {
        delete pcanvas2_;
    }

    void onDrawPaint(const SkPaint& paint) override
    {
        canvas_.drawPaint(paint);
        canvas2_.drawPaint(paint);
    }

    void onDrawRect(const SkRect& rect, const SkPaint& paint) override
    {
        canvas_.drawRect(rect, paint);
        canvas2_.drawRect(rect, paint);
    }

    void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override
    {
        canvas_.drawRRect(rrect, paint);
        canvas2_.drawRRect(rrect, paint);
    }

    void onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
                      const SkPaint& paint) override
    {
        canvas_.drawDRRect(outer, inner, paint);
        canvas2_.drawDRRect(outer, inner, paint);
    }

    void onDrawOval(const SkRect& rect, const SkPaint& paint) override
    {
        canvas_.drawOval(rect, paint);
        canvas2_.drawOval(rect, paint);
    }

    void onDrawArc(const SkRect& rect, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
                   const SkPaint& paint) override
    {
        canvas_.drawArc(rect, startAngle, sweepAngle, useCenter, paint);
        canvas2_.drawArc(rect, startAngle, sweepAngle, useCenter, paint);
    }

    void onDrawPath(const SkPath& path, const SkPaint& paint) override
    {
        canvas_.drawPath(path, paint);
        canvas2_.drawPath(path, paint);
    }

    void onDrawRegion(const SkRegion& region, const SkPaint& paint) override
    {
        canvas_.drawRegion(region, paint);
        canvas2_.drawRegion(region, paint);
    }

    void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                        const SkPaint& paint) override
    {
        canvas_.drawTextBlob(blob, x, y, paint);
        canvas2_.drawTextBlob(blob, x, y, paint);
    }

    void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
                     const SkPoint texCoords[4], SkBlendMode mode,
                     const SkPaint& paint) override
    {
        canvas_.drawPatch(cubics, colors, texCoords, mode, paint);
        canvas2_.drawPatch(cubics, colors, texCoords, mode, paint);
    }

    void onDrawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint pts[],
                      const SkPaint& paint) override
    {
        canvas_.drawPoints(mode, count, pts, paint);
        canvas2_.drawPoints(mode, count, pts, paint);
    }

    void onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
            SkCanvas::QuadAAFlags aaFlags, const SkColor4f& color, SkBlendMode mode) override
    {
        canvas_.experimental_DrawEdgeAAQuad(rect, clip, aaFlags, color, mode);
        canvas2_.experimental_DrawEdgeAAQuad(rect, clip, aaFlags, color, mode);
    }

    void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) override
    {
        canvas_.drawAnnotation(rect, key, value);
        canvas2_.drawAnnotation(rect, key, value);
    }

    void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rect) override
    {
        canvas_.private_draw_shadow_rec(path, rect);
        canvas2_.private_draw_shadow_rec(path, rect);
    }

    void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override
    {
        canvas_.drawDrawable(drawable, matrix);
        canvas2_.drawDrawable(drawable, matrix);
    }

    void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
                       const SkPaint* paint) override
    {
        canvas_.drawPicture(picture, matrix, paint);
        canvas2_.drawPicture(picture, matrix, paint);
    }

    void willSave() override
    {
        canvas_.save();
        canvas2_.save();
    }

    void willRestore() override
    {
        canvas_.restore();
        canvas2_.restore();
    }

    void onFlush() override
    {
        canvas_.flush();
        canvas2_.flush();
    }

    void didTranslate(SkScalar dx, SkScalar dy) override
    {
        canvas_.translate(dx, dy);
        canvas2_.translate(dx, dy);
    }

    void onClipRect(const SkRect& rect, SkClipOp clipOp, ClipEdgeStyle style) override
    {
        canvas_.clipRect(rect, clipOp, style);
        canvas2_.clipRect(rect, clipOp, style);
    }

    void onClipRRect(const SkRRect& rect, SkClipOp clipOp, ClipEdgeStyle style) override
    {
        canvas_.clipRRect(rect, clipOp, style);
        canvas2_.clipRRect(rect, clipOp, style);
    }

    void onClipPath(const SkPath& path, SkClipOp clipOp, ClipEdgeStyle style) override
    {
        canvas_.clipPath(path, clipOp, style);
        canvas2_.clipPath(path, clipOp, style);
    }

    void onClipRegion(const SkRegion& region, SkClipOp clipop) override
    {
        canvas_.clipRegion(region, clipop);
        canvas2_.clipRegion(region, clipop);
    }

private:
    SkCanvas &canvas_;
    SkCanvas &canvas2_;
    SkCanvas *pcanvas2_ = nullptr;
};

int main()
{
    SkiaFramework sf;
    bool debugOverdraw = false;

    SkiaControl::Button::Builder buttonBuilder;
    buttonBuilder.SetText("switch debugOverdraw");
    buttonBuilder.SetRegion(SkRect::MakeXYWH(30, 30, 0, 24));

    SkiaControl::Text::Builder textBuilder;
    textBuilder.SetText("debugOverdraw: false");
    textBuilder.SetRegion(SkRect::MakeXYWH(30, 60, 0, 0));
    auto text = textBuilder.Build();

    auto button = buttonBuilder.Build();
    button->SetOnClickFunc([&]() {
        debugOverdraw = !debugOverdraw;
        text->SetText(debugOverdraw ? "debugOverdraw: true" : "debugOverdraw: false");
    });

    sf.AddControl(button);
    sf.AddControl(text);
    sf.SetDraw2Func([&](SkCanvas &originCanvas, SkSurface &surface) {
        SkCanvas *canvas = &originCanvas;
        sk_sp<SkSurface> listenedSurface = nullptr;
        if (debugOverdraw) {
            auto info = SkImageInfo::MakeA8(surface.width(), surface.height());
            listenedSurface = surface.makeSurface(info);
            canvas = new SkOverdrawCanvas(listenedSurface->getCanvas());
            canvas = new OverdrawListenedCanvas(originCanvas, canvas);
        }

        canvas->save();
        canvas->translate(100, 100);

        SkPaint paint;
        paint.setAntiAlias(true);
        paint.setStyle(paint.kStrokeAndFill_Style);
        paint.setColor(0xffcccccc);
        canvas->drawRect(SkRect::MakeXYWH(0, 0, 400, 400), paint);
        paint.setColor(0xffdddddd);
        canvas->drawRect(SkRect::MakeXYWH(0, 0, 300, 300), paint);
        paint.setColor(0xffeeeeee);
        canvas->drawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint);
        paint.setColor(0xffffffff);
        canvas->drawRect(SkRect::MakeXYWH(0, 0, 100, 100), paint);
        paint.setColor(0xffcccccc);
        canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 200), paint);
        paint.setColor(0xff888888);
        canvas->drawRRect(SkRRect::MakeRectXY(SkRect::MakeXYWH(50, 50, 400, 300), 10, 10), paint);

        canvas->restore();

        if (debugOverdraw) {
            auto image = listenedSurface->makeImageSnapshot();

            SkPaint paint;
            const SkColor colors[] = {
                0x00000000,
                0x00000000,
                0x220000ff,
                0x2200ff00,
                0x22ff0000,
                0x44ff0000,
            };
            paint.setColorFilter(SkOverdrawColorFilter::MakeWithSkColors(colors));
            SkSamplingOptions option;
            originCanvas.drawImage(image, 0.0f, 0.0f, option, &paint);
            delete canvas;
        }
    });
    sf.Run();
    return 0;
}
